home *** CD-ROM | disk | FTP | other *** search
/ MacHack 2000 / MacHack 2000.toast / pc / The Hacks / MacHacksBug / Python 1.5.2c1 / Mac / Demo / waste / swed.py < prev    next >
Encoding:
Text File  |  2000-06-23  |  15.5 KB  |  630 lines

  1. # A minimal text editor.
  2. #
  3. # To be done:
  4. # - Functionality: find, etc.
  5.  
  6. from Menu import DrawMenuBar
  7. from FrameWork import *
  8. import Win
  9. import Qd
  10. import Res
  11. import Fm
  12. import waste
  13. import WASTEconst
  14. import Scrap
  15. import os
  16. import macfs
  17.  
  18. UNDOLABELS = [ # Indexed by WEGetUndoInfo() value
  19.     None, "", "typing", "Cut", "Paste", "Clear", "Drag", "Style"]
  20.     
  21. # Style and size menu. Note that style order is important (tied to bit values)
  22. STYLES = [
  23.     ("Bold", "B"), ("Italic", "I"), ("Underline", "U"), ("Outline", "O"),
  24.     ("Shadow", ""), ("Condensed", ""), ("Extended", "")
  25.     ]
  26. SIZES = [ 9, 10, 12, 14, 18, 24]
  27.     
  28. BIGREGION=Qd.NewRgn()
  29. Qd.SetRectRgn(BIGREGION, -16000, -16000, 16000, 16000)
  30.  
  31. class WasteWindow(ScrolledWindow):
  32.     def open(self, path, name, data):
  33.         self.path = path
  34.         self.name = name
  35.         r = windowbounds(400, 400)
  36.         w = Win.NewWindow(r, name, 1, 0, -1, 1, 0x55555555)
  37.         self.wid = w
  38.         vr = 0, 0, r[2]-r[0]-15, r[3]-r[1]-15
  39.         dr = (0, 0, 10240, 0)
  40.         Qd.SetPort(w)
  41.         Qd.TextFont(4)
  42.         Qd.TextSize(9)
  43.         flags = WASTEconst.weDoAutoScroll | WASTEconst.weDoOutlineHilite | \
  44.                 WASTEconst.weDoUndo
  45.         self.ted = waste.WENew(dr, vr, flags)
  46.         self.ted.WEInstallTabHooks()
  47.         style, soup = self.getstylesoup()
  48.         self.ted.WEInsert(data, style, soup)
  49.         self.ted.WESetSelection(0,0)
  50.         self.ted.WECalText()
  51.         self.ted.WEResetModCount()
  52.         w.DrawGrowIcon()
  53.         self.scrollbars()
  54.         self.do_postopen()
  55.         self.do_activate(1, None)
  56.         
  57.     def getstylesoup(self):
  58.         if not self.path:
  59.             return None, None
  60.         oldrf = Res.CurResFile()
  61.         try:
  62.             rf = Res.OpenResFile(self.path)
  63.         except Res.Error:
  64.             return None, None
  65.         try:
  66.             hstyle = Res.Get1Resource('styl', 128)
  67.             hstyle.DetachResource()
  68.         except Res.Error:
  69.             hstyle = None
  70.         try:
  71.             hsoup = Res.Get1Resource('SOUP', 128)
  72.             hsoup.DetachResource()
  73.         except Res.Error:
  74.             hsoup = None
  75.         Res.CloseResFile(rf)
  76.         Res.UseResFile(oldrf)
  77.         return hstyle, hsoup
  78.                 
  79.     def do_idle(self, event):
  80.         (what, message, when, where, modifiers) = event
  81.         Qd.SetPort(self.wid)
  82.         self.ted.WEIdle()    
  83.         if self.ted.WEAdjustCursor(where, BIGREGION):
  84.             return
  85.         Qd.SetCursor(Qd.qd.arrow)
  86.         
  87.     def getscrollbarvalues(self):
  88.         dr = self.ted.WEGetDestRect()
  89.         vr = self.ted.WEGetViewRect()
  90.         vx = self.scalebarvalue(dr[0], dr[2], vr[0], vr[2])
  91.         vy = self.scalebarvalue(dr[1], dr[3], vr[1], vr[3])
  92.         return vx, vy
  93.         
  94.     def scrollbar_callback(self, which, what, value):
  95.         if which == 'y':
  96.             if what == 'set':
  97.                 height = self.ted.WEGetHeight(0, 0x3fffffff)
  98.                 cur = self.getscrollbarvalues()[1]
  99.                 delta = (cur-value)*height/32767
  100.             if what == '-':
  101.                 topline_off,dummy = self.ted.WEGetOffset((1,1))
  102.                 topline_num = self.ted.WEOffsetToLine(topline_off)
  103.                 delta = self.ted.WEGetHeight(topline_num, topline_num+1)
  104.             elif what == '--':
  105.                 delta = (self.ted.WEGetViewRect()[3]-10)
  106.                 if delta <= 0:
  107.                     delta = 10 # Random value
  108.             elif what == '+':
  109.                 # XXXX Wrong: should be bottom line size
  110.                 topline_off,dummy = self.ted.WEGetOffset((1,1))
  111.                 topline_num = self.ted.WEOffsetToLine(topline_off)
  112.                 delta = -self.ted.WEGetHeight(topline_num, topline_num+1)
  113.             elif what == '++':
  114.                 delta = -(self.ted.WEGetViewRect()[3]-10)
  115.                 if delta >= 0:
  116.                     delta = -10
  117.             self.ted.WEScroll(0, delta)
  118.         else:
  119.             if what == 'set':
  120.                 return # XXXX
  121.             vr = self.ted.WEGetViewRect()
  122.             winwidth = vr[2]-vr[0]
  123.             if what == '-':
  124.                 delta = winwidth/10
  125.             elif what == '--':
  126.                 delta = winwidth/2
  127.             elif what == '+':
  128.                 delta = -winwidth/10
  129.             elif what == '++':
  130.                 delta = -winwidth/2
  131.             self.ted.WEScroll(delta, 0)
  132.         # Pin the scroll
  133.         l, t, r, b = self.ted.WEGetDestRect()
  134.         vl, vt, vr, vb = self.ted.WEGetViewRect()
  135.         if t > 0 or l > 0:
  136.             dx = dy = 0
  137.             if t > 0: dy = -t
  138.             if l > 0: dx = -l
  139.             self.ted.WEScroll(dx, dy)
  140.         elif b < vb:
  141.             self.ted.WEScroll(0, b-vb)
  142.  
  143.         
  144.     def do_activate(self, onoff, evt):
  145.         Qd.SetPort(self.wid)
  146.         ScrolledWindow.do_activate(self, onoff, evt)
  147.         if onoff:
  148.             self.ted.WEActivate()
  149.             self.parent.active = self
  150.             self.parent.updatemenubar()
  151.         else:
  152.             self.ted.WEDeactivate()
  153.  
  154.     def do_update(self, wid, event):
  155.         region = wid.GetWindowPort().visRgn
  156.         if Qd.EmptyRgn(region):
  157.             return
  158.         Qd.EraseRgn(region)
  159.         self.ted.WEUpdate(region)
  160.         self.updatescrollbars()
  161.         
  162.     def do_postresize(self, width, height, window):
  163.         l, t, r, b = self.ted.WEGetViewRect()
  164.         vr = (l, t, l+width-15, t+height-15)
  165.         self.ted.WESetViewRect(vr)
  166.         Win.InvalRect(vr)
  167.         ScrolledWindow.do_postresize(self, width, height, window)
  168.         
  169.     def do_contentclick(self, local, modifiers, evt):
  170.         (what, message, when, where, modifiers) = evt
  171.         self.ted.WEClick(local, modifiers, when)
  172.         self.updatescrollbars()
  173.         self.parent.updatemenubar()
  174.  
  175.     def do_char(self, ch, event):
  176.         self.ted.WESelView()
  177.         (what, message, when, where, modifiers) = event
  178.         self.ted.WEKey(ord(ch), modifiers)
  179.         self.updatescrollbars()
  180.         self.parent.updatemenubar()
  181.         
  182.     def close(self):
  183.         if self.ted.WEGetModCount():
  184.             save = EasyDialogs.AskYesNoCancel('Save window "%s" before closing?'%self.name, 1)
  185.             if save > 0:
  186.                 self.menu_save()
  187.             elif save < 0:
  188.                 return
  189.         if self.parent.active == self:
  190.             self.parent.active = None
  191.         self.parent.updatemenubar()
  192.         del self.ted
  193.         self.do_postclose()
  194.         
  195.     def menu_save(self):
  196.         if not self.path:
  197.             self.menu_save_as()
  198.             return # Will call us recursively
  199.         #
  200.         # First save data
  201.         #
  202.         dhandle = self.ted.WEGetText()
  203.         data = dhandle.data
  204.         fp = open(self.path, 'wb')  # NOTE: wb, because data has CR for end-of-line
  205.         fp.write(data)
  206.         if data[-1] <> '\r': fp.write('\r')
  207.         fp.close()
  208.         #
  209.         # Now save style and soup
  210.         #
  211.         oldresfile = Res.CurResFile()
  212.         try:
  213.             rf = Res.OpenResFile(self.path)
  214.         except Res.Error:
  215.             Res.CreateResFile(self.path)
  216.             rf = Res.OpenResFile(self.path)
  217.         styles = Res.Resource('')
  218.         soup = Res.Resource('')
  219.         self.ted.WECopyRange(0, 0x3fffffff, None, styles, soup)
  220.         styles.AddResource('styl', 128, '')
  221.         soup.AddResource('SOUP', 128, '')
  222.         Res.CloseResFile(rf)
  223.         Res.UseResFile(oldresfile)
  224.         
  225.         self.ted.WEResetModCount()
  226.         
  227.     def menu_save_as(self):
  228.         fss, ok = macfs.StandardPutFile('Save as:')
  229.         if not ok: return
  230.         self.path = fss.as_pathname()
  231.         self.name = os.path.split(self.path)[-1]
  232.         self.wid.SetWTitle(self.name)
  233.         self.menu_save()
  234.         
  235.     def menu_cut(self):
  236.         self.ted.WESelView()
  237.         self.ted.WECut()
  238.         Scrap.ZeroScrap()
  239.         self.ted.WECut()
  240.         self.updatescrollbars()
  241.         self.parent.updatemenubar()
  242.         
  243.     def menu_copy(self):
  244.         Scrap.ZeroScrap()
  245.         self.ted.WECopy()
  246.         self.updatescrollbars()
  247.         self.parent.updatemenubar()
  248.         
  249.     def menu_paste(self):
  250.         self.ted.WESelView()
  251.         self.ted.WEPaste()
  252.         self.updatescrollbars()
  253.         self.parent.updatemenubar()
  254.         
  255.     def menu_clear(self):
  256.         self.ted.WESelView()
  257.         self.ted.WEDelete()
  258.         self.updatescrollbars()
  259.         self.parent.updatemenubar()
  260.  
  261.     def menu_undo(self):
  262.         self.ted.WEUndo()
  263.         self.updatescrollbars()
  264.         self.parent.updatemenubar()
  265.         
  266.     def menu_setfont(self, font):
  267.         font = Fm.GetFNum(font)
  268.         self.mysetstyle(WASTEconst.weDoFont, (font, 0, 0, (0,0,0)))
  269.         self.parent.updatemenubar()
  270.                 
  271.     def menu_modface(self, face):
  272.         self.mysetstyle(WASTEconst.weDoFace|WASTEconst.weDoToggleFace, 
  273.             (0, face, 0, (0,0,0)))
  274.  
  275.     def menu_setface(self, face):
  276.         self.mysetstyle(WASTEconst.weDoFace|WASTEconst.weDoReplaceFace, 
  277.             (0, face, 0, (0,0,0)))
  278.  
  279.     def menu_setsize(self, size):
  280.         self.mysetstyle(WASTEconst.weDoSize, (0, 0, size, (0,0,0)))
  281.                                 
  282.     def menu_incsize(self, size):
  283.         self.mysetstyle(WASTEconst.weDoAddSize, (0, 0, size, (0,0,0)))
  284.  
  285.     def mysetstyle(self, which, how):
  286.         self.ted.WESelView()
  287.         self.ted.WESetStyle(which, how)
  288.         self.parent.updatemenubar()
  289.                                 
  290.     def have_selection(self):
  291.         start, stop = self.ted.WEGetSelection()
  292.         return start < stop
  293.         
  294.     def can_paste(self):
  295.         return self.ted.WECanPaste()
  296.         
  297.     def can_undo(self):
  298.         which, redo = self.ted.WEGetUndoInfo()
  299.         which = UNDOLABELS[which]
  300.         if which == None: return None
  301.         if redo:
  302.             return "Redo "+which
  303.         else:
  304.             return "Undo "+which
  305.             
  306.     def getruninfo(self):
  307.         all = (WASTEconst.weDoFont | WASTEconst.weDoFace | WASTEconst.weDoSize)
  308.         dummy, mode, (font, face, size, color) = self.ted.WEContinuousStyle(all)
  309.         if not (mode & WASTEconst.weDoFont):
  310.             font = None
  311.         else:
  312.             font = Fm.GetFontName(font)
  313.         if not (mode & WASTEconst.weDoFace): fact = None
  314.         if not (mode & WASTEconst.weDoSize): size = None
  315.         return font, face, size
  316.  
  317. class Wed(Application):
  318.     def __init__(self):
  319.         Application.__init__(self)
  320.         self.num = 0
  321.         self.active = None
  322.         self.updatemenubar()
  323.         waste.STDObjectHandlers()
  324.         
  325.     def makeusermenus(self):
  326.         self.filemenu = m = Menu(self.menubar, "File")
  327.         self.newitem = MenuItem(m, "New window", "N", self.open)
  328.         self.openitem = MenuItem(m, "Open...", "O", self.openfile)
  329.         self.closeitem = MenuItem(m, "Close", "W", self.closewin)
  330.         m.addseparator()
  331.         self.saveitem = MenuItem(m, "Save", "S", self.save)
  332.         self.saveasitem = MenuItem(m, "Save as...", "", self.saveas)
  333.         m.addseparator()
  334.         self.quititem = MenuItem(m, "Quit", "Q", self.quit)
  335.         
  336.         self.editmenu = m = Menu(self.menubar, "Edit")
  337.         self.undoitem = MenuItem(m, "Undo", "Z", self.undo)
  338.         self.cutitem = MenuItem(m, "Cut", "X", self.cut)
  339.         self.copyitem = MenuItem(m, "Copy", "C", self.copy)
  340.         self.pasteitem = MenuItem(m, "Paste", "V", self.paste)
  341.         self.clearitem = MenuItem(m, "Clear", "", self.clear)
  342.         
  343.         self.makefontmenu()
  344.         
  345.         # Groups of items enabled together:
  346.         self.windowgroup = [self.closeitem, self.saveitem, self.saveasitem,
  347.             self.editmenu, self.fontmenu, self.facemenu, self.sizemenu]
  348.         self.focusgroup = [self.cutitem, self.copyitem, self.clearitem]
  349.         self.windowgroup_on = -1
  350.         self.focusgroup_on = -1
  351.         self.pastegroup_on = -1
  352.         self.undo_label = "never"
  353.         self.ffs_values = ()
  354.         
  355.     def makefontmenu(self):
  356.         self.fontmenu = Menu(self.menubar, "Font")
  357.         self.fontnames = getfontnames()
  358.         self.fontitems = []
  359.         for n in self.fontnames:
  360.             m = MenuItem(self.fontmenu, n, "", self.selfont)
  361.             self.fontitems.append(m)
  362.         self.facemenu = Menu(self.menubar, "Style")
  363.         self.faceitems = []
  364.         for n, shortcut in STYLES:
  365.             m = MenuItem(self.facemenu, n, shortcut, self.selface)
  366.             self.faceitems.append(m)
  367.         self.facemenu.addseparator()
  368.         self.faceitem_normal = MenuItem(self.facemenu, "Normal", "N", 
  369.             self.selfacenormal)
  370.         self.sizemenu = Menu(self.menubar, "Size")
  371.         self.sizeitems = []
  372.         for n in SIZES:
  373.             m = MenuItem(self.sizemenu, `n`, "", self.selsize)
  374.             self.sizeitems.append(m)
  375.         self.sizemenu.addseparator()
  376.         self.sizeitem_bigger = MenuItem(self.sizemenu, "Bigger", "+", 
  377.             self.selsizebigger)
  378.         self.sizeitem_smaller = MenuItem(self.sizemenu, "Smaller", "-", 
  379.             self.selsizesmaller)
  380.                     
  381.     def selfont(self, id, item, *rest):
  382.         if self.active:
  383.             font = self.fontnames[item-1]
  384.             self.active.menu_setfont(font)
  385.         else:
  386.             EasyDialogs.Message("No active window?")
  387.  
  388.     def selface(self, id, item, *rest):
  389.         if self.active:
  390.             face = (1<<(item-1))
  391.             self.active.menu_modface(face)
  392.         else:
  393.             EasyDialogs.Message("No active window?")
  394.  
  395.     def selfacenormal(self, *rest):
  396.         if self.active:
  397.             self.active.menu_setface(0)
  398.         else:
  399.             EasyDialogs.Message("No active window?")
  400.  
  401.     def selsize(self, id, item, *rest):
  402.         if self.active:
  403.             size = SIZES[item-1]
  404.             self.active.menu_setsize(size)
  405.         else:
  406.             EasyDialogs.Message("No active window?")
  407.  
  408.     def selsizebigger(self, *rest):
  409.         if self.active:
  410.             self.active.menu_incsize(2)
  411.         else:
  412.             EasyDialogs.Message("No active window?")
  413.  
  414.     def selsizesmaller(self, *rest):
  415.         if self.active:
  416.             self.active.menu_incsize(-2)
  417.         else:
  418.             EasyDialogs.Message("No active window?")
  419.  
  420.     def updatemenubar(self):
  421.         changed = 0
  422.         on = (self.active <> None)
  423.         if on <> self.windowgroup_on:
  424.             for m in self.windowgroup:
  425.                 m.enable(on)
  426.             self.windowgroup_on = on
  427.             changed = 1
  428.         if on:
  429.             # only if we have an edit menu
  430.             on = self.active.have_selection()
  431.             if on <> self.focusgroup_on:
  432.                 for m in self.focusgroup:
  433.                     m.enable(on)
  434.                 self.focusgroup_on = on
  435.                 changed = 1
  436.             on = self.active.can_paste()
  437.             if on <> self.pastegroup_on:
  438.                 self.pasteitem.enable(on)
  439.                 self.pastegroup_on = on
  440.                 changed = 1
  441.             on = self.active.can_undo()
  442.             if on <> self.undo_label:
  443.                 if on:
  444.                     self.undoitem.enable(1)
  445.                     self.undoitem.settext(on)
  446.                     self.undo_label = on
  447.                 else:
  448.                     self.undoitem.settext("Nothing to undo")
  449.                     self.undoitem.enable(0)
  450.                 changed = 1
  451.             if self.updatefontmenus():
  452.                 changed = 1
  453.         if changed:
  454.             DrawMenuBar()
  455.             
  456.     def updatefontmenus(self):
  457.         info = self.active.getruninfo()
  458.         if info == self.ffs_values:
  459.             return 0
  460.         # Remove old checkmarks
  461.         if self.ffs_values == ():
  462.             self.ffs_values = (None, None, None)
  463.         font, face, size = self.ffs_values
  464.         if font <> None:
  465.             fnum = self.fontnames.index(font)
  466.             self.fontitems[fnum].check(0)
  467.         if face <> None:
  468.             for i in range(len(self.faceitems)):
  469.                 if face & (1<<i):
  470.                     self.faceitems[i].check(0)
  471.         if size <> None:
  472.             for i in range(len(self.sizeitems)):
  473.                 if SIZES[i] == size:
  474.                     self.sizeitems[i].check(0)
  475.                 
  476.         self.ffs_values = info
  477.         # Set new checkmarks
  478.         font, face, size = self.ffs_values
  479.         if font <> None:
  480.             fnum = self.fontnames.index(font)
  481.             self.fontitems[fnum].check(1)
  482.         if face <> None:
  483.             for i in range(len(self.faceitems)):
  484.                 if face & (1<<i):
  485.                     self.faceitems[i].check(1)
  486.         if size <> None:
  487.             for i in range(len(self.sizeitems)):
  488.                 if SIZES[i] == size:
  489.                     self.sizeitems[i].check(1)
  490.         # Set outline/normal for sizes
  491.         if font:
  492.             exists = getfontsizes(font, SIZES)
  493.             for i in range(len(self.sizeitems)):
  494.                 if exists[i]:
  495.                     self.sizeitems[i].setstyle(0)
  496.                 else:
  497.                     self.sizeitems[i].setstyle(8)
  498.  
  499.     #
  500.     # Apple menu
  501.     #
  502.     
  503.     def do_about(self, id, item, window, event):
  504.         EasyDialogs.Message("A simple single-font text editor based on WASTE")
  505.             
  506.     #
  507.     # File menu
  508.     #
  509.  
  510.     def open(self, *args):
  511.         self._open(0)
  512.         
  513.     def openfile(self, *args):
  514.         self._open(1)
  515.  
  516.     def _open(self, askfile):
  517.         if askfile:
  518.             fss, ok = macfs.StandardGetFile('TEXT')
  519.             if not ok:
  520.                 return
  521.             path = fss.as_pathname()
  522.             name = os.path.split(path)[-1]
  523.             try:
  524.                 fp = open(path, 'rb') # NOTE binary, we need cr as end-of-line
  525.                 data = fp.read()
  526.                 fp.close()
  527.             except IOError, arg:
  528.                 EasyDialogs.Message("IOERROR: "+`arg`)
  529.                 return
  530.         else:
  531.             path = None
  532.             name = "Untitled %d"%self.num
  533.             data = ''
  534.         w = WasteWindow(self)
  535.         w.open(path, name, data)
  536.         self.num = self.num + 1
  537.         
  538.     def closewin(self, *args):
  539.         if self.active:
  540.             self.active.close()
  541.         else:
  542.             EasyDialogs.Message("No active window?")
  543.         
  544.     def save(self, *args):
  545.         if self.active:
  546.             self.active.menu_save()
  547.         else:
  548.             EasyDialogs.Message("No active window?")
  549.         
  550.     def saveas(self, *args):
  551.         if self.active:
  552.             self.active.menu_save_as()
  553.         else:
  554.             EasyDialogs.Message("No active window?")
  555.             
  556.         
  557.     def quit(self, *args):
  558.         for w in self._windows.values():
  559.             w.close()
  560.         if self._windows:
  561.             return
  562.         self._quit()
  563.         
  564.     #
  565.     # Edit menu
  566.     #
  567.     
  568.     def undo(self, *args):
  569.         if self.active:
  570.             self.active.menu_undo()
  571.         else:
  572.             EasyDialogs.Message("No active window?")
  573.         
  574.     def cut(self, *args):
  575.         if self.active:
  576.             self.active.menu_cut()
  577.         else:
  578.             EasyDialogs.Message("No active window?")
  579.         
  580.     def copy(self, *args):
  581.         if self.active:
  582.             self.active.menu_copy()
  583.         else:
  584.             EasyDialogs.Message("No active window?")
  585.         
  586.     def paste(self, *args):
  587.         if self.active:
  588.             self.active.menu_paste()
  589.         else:
  590.             EasyDialogs.Message("No active window?")
  591.  
  592.     def clear(self, *args):
  593.         if self.active:
  594.             self.active.menu_clear()
  595.         else:
  596.             EasyDialogs.Message("No active window?")
  597.         
  598.     #
  599.     # Other stuff
  600.     #    
  601.  
  602.     def idle(self, event):
  603.         if self.active:
  604.             self.active.do_idle(event)
  605.             
  606. def getfontnames():
  607.     names = []
  608.     for i in range(256):
  609.         n = Fm.GetFontName(i)
  610.         if n: names.append(n)
  611.     return names
  612.     
  613. def getfontsizes(name, sizes):
  614.     exist = []
  615.     num = Fm.GetFNum(name)
  616.     for sz in sizes:
  617.         if Fm.RealFont(num, sz):
  618.             exist.append(1)
  619.         else:
  620.             exist.append(0)
  621.     return exist
  622.  
  623. def main():
  624.     App = Wed()
  625.     App.mainloop()
  626.     
  627. if __name__ == '__main__':
  628.     main()
  629.     
  630.